﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using System.Reflection;
using System.Collections;

namespace ilanver.Core.DomainModel
{
    [Serializable]
    [JsonObject(MemberSerialization.OptIn)]
    public abstract class BaseObject
    {
        [ThreadStatic]
        private static Dictionary<Type, IEnumerable<PropertyInfo>> signaturePropertiesDictionary;

        private const int HASH_MULTIPLIER = 31;

        public override bool Equals(object obj)
        {
            BaseObject compareTo = obj as BaseObject;

            if (ReferenceEquals(this, compareTo))
                return true;

            return compareTo != null && GetType().Equals(compareTo.GetTypeUnproxied()) && HasSameObjectSignatureAs(compareTo);
        }


        public override int GetHashCode()
        {
            unchecked
            {
                IEnumerable<PropertyInfo> signatureProperties = GetSignatureProperties();

                int hasCode = GetType().GetHashCode();

                foreach (PropertyInfo property in signatureProperties)
                {
                    object value = property.GetValue(this, null);

                    if (value != null)
                        hasCode = (hasCode * HASH_MULTIPLIER) ^ value.GetHashCode();
                }

                if (signatureProperties.Any())
                    return hasCode;

                return base.GetHashCode();
            }
        }

        public virtual IEnumerable<PropertyInfo> GetSignatureProperties()
        {
            IEnumerable<PropertyInfo> properties;
            if (signaturePropertiesDictionary == null)
                signaturePropertiesDictionary = new Dictionary<Type, IEnumerable<PropertyInfo>>();

            if (signaturePropertiesDictionary.TryGetValue(GetType(), out properties))
                return properties;

            return (signaturePropertiesDictionary[GetType()] = GetTypeSpecificSignatureProperties());
        }

        protected abstract IEnumerable<PropertyInfo> GetTypeSpecificSignatureProperties();

        public virtual bool HasSameObjectSignatureAs(BaseObject compareTo)
        {
            IEnumerable<PropertyInfo> signatureProperties = GetSignatureProperties();

            foreach (PropertyInfo property in signatureProperties)
            {
                object valueOfThisObject = property.GetValue(this, null);
                object valueToCompareTo = property.GetValue(compareTo, null);

                if (valueOfThisObject != null && valueToCompareTo != null)
                    continue;

                if ((valueOfThisObject == null ^ valueToCompareTo == null) || (!valueOfThisObject.Equals(valueToCompareTo)))
                    return false;

            }

            return signatureProperties.Any() || base.Equals(compareTo);
        }

        protected virtual Type GetTypeUnproxied()
        {
            return GetType();
        }
    }
}
